#pragma once
#include "bencode.h"
#include "bt.h"
#include "file.h"
#include "routing_table.h"
#include "socket.h"
#include "task.h"
#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <mutex>
#include <mysql-cppconn/mysqlx/xdevapi.h>
#include <queue>
#include <span>
#include <string>
#include <thread>
#include <unordered_map>
#include <utility>
#include <vector>

namespace cobt {

class SendQueue {
public:
  SendQueue() : m_thread(&SendQueue::run, this), m_stop(false) {}
  ~SendQueue() {
    m_stop = true;
    m_cv.notify_all();
    m_thread.join();
  }
  void push(std::function<void()> &&cb) {
    {
      std::lock_guard<std::mutex> lock(m_mutex);
      m_queue.push(std::move(cb));
    }
    m_cv.notify_one();
  }

private:
  std::queue<std::function<void()>> m_queue;
  std::mutex m_mutex;
  std::thread m_thread;
  std::condition_variable m_cv;
  std::atomic_bool m_stop;

  void run() {
    while (true) {
      std::function<void()> cb;
      {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_cv.wait(lock, [this] { return !m_queue.empty() || m_stop; });
        if (m_stop && m_queue.empty()) {
          return;
        }
        cb = std::move(m_queue.front());
        m_queue.pop();
      }
      cb();
    }
  }
};

class DHTDigger {
  friend class GetMetaFile;

public:
  DHTDigger(mysqlx::Table &m_table);

  void listen(uint16_t port);

private:
  void join();
  Task<> init();
  void get_metadata_loop();

  std::string insert_id(const SocketAddress &, const BEncodeData &a);
  Task<> get_metadata(DHTNode node);
  void insert_peer(DHTNode node);
  bool info_hash_status(const std::string &info_hash) const;
  void set_info_hash(const std::string &info_hash, bool status,
                     const std::string &name = "");
  void handle_response(const BEncodeData &bdata, const SocketAddress &addr);
  void handle_find_node_response(const std::string &id, const BEncodeData &r,
                                 const std::string &key, int step);
  void handle_get_peers_response_node(const std::string &id,
                                      const BEncodeData &r,
                                      const std::string &key, int step,
                                      const SocketAddress &addr);
  void handle_get_peers_response_peer(const std::string &id,
                                      const BEncodeData &values,
                                      const SocketAddress &addr);

  void handle_request(const BEncodeData &bdata, const SocketAddress &addr);
  void handle_ping(const BEncodeData &t, const BEncodeData &a,
                     const SocketAddress &addr);
  void handle_get_peers(const BEncodeData &t, const BEncodeData &a,
                          const SocketAddress &addr);
  void handle_find_node(const BEncodeData &t, const BEncodeData &a,
                          const SocketAddress &addr);
  void handle_announce_peer(const BEncodeData &t, const BEncodeData &a,
                              const SocketAddress &addr);

  void handle_recv(std::span<char> buf, const SocketAddress &addr);

  std::optional<BMap> get_closet_nodes(const std::string &id);

  static std::string GenToken(const std::string &info_hash,
                              int len = TOKEN_SIZE);
  static std::string GenFakeId(const std::string &id);

  std::string m_id;
  std::vector<SocketAddress> m_nodes;
  int m_fd;
  FileHandle m_file;

  std::unordered_map<std::string, std::string> m_info_hashs;

  std::queue<DHTNode> m_peers_queue;
  std::queue<DHTNode> m_nodes_queue;
  RoutingTable m_rt;
  SendQueue m_send_queue;
  mysqlx::Table &m_table;

  static constexpr size_t MAX_QUEUE_SIZE = 8192;
  static constexpr size_t MAX_PEER_SIZE = 64;
  static constexpr size_t MAX_NODE_QUEUE_SIZE = 64;
  static constexpr int TOKEN_SIZE = 8;
  static constexpr size_t COBT_MAX_CONNECTIONS = 256;
  static constexpr size_t MAX_INFO_HASH_SIZE = 512;
};
} // namespace cobt